import * as connectionManager from '../connectionManager';
import logger from '../../../common/logger';
import _ from 'lodash';
import { eventBus } from '../eventBus';
import { SimJobStatus } from '../../model/simulation/type';
import { SimulationJobDto } from '../../model/simulation/dtos';
import { SimulationRequest } from './dispatchRequest';
import { JobDispatchedEvent } from '../../../domain/event/jobDispatched';
import { ISimulationJobService } from '../../model/simulation/service';

/**
 *  responsible to dispatch simluation request from node with status AgentAssigned
 */
export class SimluationDispatchService {

  private simulationJobService: ISimulationJobService;
  private scanInterval: number;

  constructor(simJobService: ISimulationJobService, scanInterval: number = 5000) {
    this.simulationJobService = simJobService;
    this.scanInterval = scanInterval;
  }

  init() {
    setInterval(() => {
      this.processAgentAssignedJob();
    }, this.scanInterval);
  }

  async processAgentAssignedJob() {
    const jobs = await this.simulationJobService.findByStatus(SimJobStatus.AgentAssigned);
    for (const job of jobs) {
      this.dispatch(job);
    }
  }

  async dispatch(job: SimulationJobDto) {
    const agentIdentity = job.agentId;
    const connection = connectionManager.getConnection(agentIdentity);
    if (connection) {
      try {
        const request = this.buildRequestFromJob(job);
        connection.emit('simulation:run', request, async (res) => {
          job.status = SimJobStatus.AgentDispatched;
          await this.simulationJobService.save(job);
          eventBus.emit(JobDispatchedEvent.NAME, new JobDispatchedEvent(job));
        });
      } catch (e) {
        logger.error(`dispatch simulation job(id=${job.id}) to agent(id=${agentIdentity}) failed ${e.message} ${e.stack}`);
      }
    }
  }

  async restart(job: SimulationJobDto) {
    const agentIdentity = job.agentId;
    const connection = connectionManager.getConnection(agentIdentity);
    if (connection) {
      try {
        const request = this.buildRequestFromJob(job);
        connection.emit('simulation:restart', request, async (res) => {
          job.status = SimJobStatus.AgentDispatched;
          await this.simulationJobService.save(job);
          eventBus.emit(JobDispatchedEvent.NAME, new JobDispatchedEvent(job));
        });
      } catch (e) {
        logger.error(`dispatch simulation job(id=${job.id}) to agent(id=${agentIdentity}) failed ${e.message} ${e.stack}`);
      }
    }
  }

  // actually send to agent request body
  buildRequestFromJob(job: SimulationJobDto): SimulationRequest {
    const jobId = _.toString(job.id);
    const request = {} as SimulationRequest;
    request.id = jobId;
    request.product = job.product;
    request.context = { userId: job.userId, appId: job.appId, nodeId: job.nodeId, jobId, debug: job.debug ?? false };
    request.definition = job.spec;
    return request;
  }
}